#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
#include <unistd.h>
#include "srtp.h"

/******* Server Functions *******/

// Initialize spcb
struct srtp_server_pcb f;

// sockaddr_in for listen_info
struct sockaddr_in listen_info;

// sockaddr_in for client_info
struct sockaddr_in client_info;

// Initializes the server components
int server_port_num;
int socket_server;

// Temporary buffer for data to receive/send
char buffer[SRTP_MAX_PAYLOAD_SIZE];
char str[SRTP_MAX_PAYLOAD_SIZE];

// create datagram to store received info  
ssize_t datagram;

// Initialize a socket for the client
socklen_t fSockLen;

// For Testing
int test = 0;
int global = 2;

// Getting system time for file write out name
char *date;
char *new_date;
time_t timer;

int test_mode = 0;

int sent_counter = 1;

// define num_threads   5

// creating the code for the tasks for the thread	
// void *thread_task(void *argument){
// int tid;
// tid = *((int*)argument);
// printf("thread executinng\n", tid);
// server_port_num = server_port_num++;
//      srtp_listen(&f, server_port_num);

// return NULL;
//}


/*
 Function:   srtp_listen
 Purpose:	The function opens a UDP socket on the specified port and initialises the server PCB accordingly.
 
 Parameters: 
 spcb	: server PCB 
 port	: port number on which server is listening
 
 Returns:     0 on success, -1 on failure
 */
int srtp_listen(struct srtp_server_pcb *spcb, int port){
    //concurrency
    
    //     pthread_t threads[num_threads];
    //  int thread_args[num_threads];
    //  int rc, i;
    //  i=0;
    //  if (i<num_threads){
    //    thread_args[i] = i;
    //   printf("creating thread in listen\n");
    //   rc = pthread_create(&threads[i],NULL,thread_task,(void*)&thread_args[i]);
    //   assert(0==rc);
    // }
    
    // Setting time
    timer = time(NULL);
    date = asctime(localtime(&timer));
    new_date = date;
    
    // Setting server state to 5 after when called srtp_listen
    f.state = 5;
    printf(">> The state is now LISTEN - %d\n", f.state);
    
    // Set listen_info details
	listen_info.sin_family = AF_INET;
	listen_info.sin_addr.s_addr = INADDR_ANY; 
	listen_info.sin_port = htons(f.server_port_num_pcb);
	bzero(&(listen_info.sin_zero),8);
	fSockLen = sizeof(listen_info);
	
	// Create a socket 
    socket_server = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
	
    // Send info to spcb
    f.sockfd = socket_server;
    printf("The socket is  %d\n",socket_server);
    
    int bind_call;
    bind_call = bind(f.sockfd,(struct sockaddr*)&listen_info, sizeof(struct sockaddr));
    if (bind_call < 0){
        perror("Error 1");
	}
    
    for(;;){
        // Receiving
        printf ("Waiting for datagram....\n");
        // Calls srtp_accept to accept the connection
        srtp_accept(&f);
	}  
}

/*
 Function:   srtp_accept
 Purpose:	The function blocks and waits for SYN packets. It performs the 2-way connection establishment handshake.
 The function can only be called if the connection is in state LISTEN. The function updates the server PCB
 according to the outcome of the 2-way handshake.
 
 Parameters: 
 spcb	: server PCB  
 
 Returns:     0 on success, -1 on failure
 */

int srtp_accept(struct srtp_server_pcb *spcb){
    
    // Create buffer to store things
    struct pktbuf temp;
    
    // Create serv_hdr to store header info
    struct srtp_hdr serv_hdr; 
    
    // Create ack_packet
    struct pktbuf ack_packet;
    
    // Create receiving header
    struct srtp_hdr rec_hdr;
    
    // Create an ack_packet for receiving
    struct pktbuf ack_packet_rec; 
    pid_t pid;
    
    // Creating header for the syn packet 
    serv_hdr.type = 2;
    serv_hdr.seqnbr = 0;
    
    // Filling in syn packet with info
    ack_packet.serv_hdr = serv_hdr;
    // ack_packet_rec.serv_hdr.seqnbr = 1;
    // not sending anything
    ack_packet.len = 0;
    ack_packet.payload[0];
    
    // int connfd;
    // f.connfd = connfd;
    // connfd = recvfrom(f.sockfd, (struct pktbuf *)&temp,SRTP_MAX_PAYLOAD_SIZE+20, 0, (struct sockaddr *)&client_info, &fSockLen);
    
    
    // Set listen_info details
    //        current_port_num = f.server_port_num_pcb;//creating ports for concurrency
    //    current_port_num++;
    //f.server_port_num_pcb = current_port_num;
    //listen_info.sin_family = AF_INET;
    //listen_info.sin_addr.s_addr = INADDR_ANY; 
    //listen_info.sin_port = htons(f.server_port_num_pcb);
    //bzero(&(listen_info.sin_zero),8);
    //fSockLen = sizeof(listen_info);
    
    //     socket_server = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);// created a socket 
    //        f.sockfd = socket_server;//send info to spcb
    //       printf("The socket is  %d\n",socket_server);
    
    
    // if ((pid = fork()) == 0 ){
    //	 close(f.sockfd);
    //  int process = 0;
    // process++;
    // printf("this is process number %d\n\n", process);
    int z = 0;
    while(z!=1){
        struct timeval tv;
        //waiting for 10 seconds
        tv.tv_sec = 10;
        tv.tv_usec = 0;
        
        fd_set readfds, masterfds;
        
        FD_ZERO(&masterfds);
        FD_SET(f.sockfd, &masterfds);
        
        memcpy(&readfds, &masterfds, sizeof(fd_set));
        printf(">> Waiting......\n");
        if (select(f.sockfd+1, &readfds, NULL, NULL, &tv) < 0) {
            perror("|| On SRTP_ACCEPT Select");
            exit(1);
        }
        
        if (FD_ISSET(f.sockfd, &readfds)) {
            //setting datagram = info received
            
            datagram = recvfrom(f.sockfd, (struct pktbuf *)&temp,SRTP_MAX_PAYLOAD_SIZE+20, 0, (struct sockaddr *)&client_info, &fSockLen);
            z = 1;
            
	        if (datagram == -1) {
                //check for error
                perror("|| On SRTP_ACCEPT recvfrom");
                exit(EXIT_FAILURE);
            }
        }
	}      
    
    
    
    // ? Remove ? : receiving concurrently
    
    // If there is nothing received
    if (datagram < 0) {
        //check for error
        perror("|| On SRTP_ACCEPT Size 0 Datagram");
        exit(EXIT_FAILURE);
    }
    
    //If there is something received
    else{
        
        // Check if header type is 4, which is a FIN packet
        if(temp.hdr.type == 4){
            
            // Upon receiving a fin packet, send back an ack packet
            int fin_send_ack = sendto(socket_server, &ack_packet, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));
            timer = time(NULL);
            printf("** new_date at fin is %s\n", new_date);
            
            // Setting the date
            date = asctime(localtime(&timer));
            new_date = date;
            
            // Clean up counters and put them back to original state
            f.seqnum_client = 1;
            f.last_return = 1;
            test = 0;
            
            // Reset connection upon finish
            // srtp_server_reset(&f);  
            // srtp_listen(&f, server_port_num);
            // srtp_accept(&f);
            
            if (fin_send_ack < 0) {
                // Testing for error
                perror("|| On SRTP_ACCEPT Size 0 Datagram");
                exit(EXIT_FAILURE);
            }
            else {
                //setting server state to  after sending ack
                f.state = 6;
                printf(">> The state is now TIME_WAIT - %d\n", f.state);
                
                sleep(10);
                
                f.state = 2;
                printf(">> The state is now CLOSED - %d\n", f.state);
            }
        }       
    }
    
    sleep(1);
    
    // sending
    // create header
    // creating syn packet in header
    rec_hdr.type = 2; 
    // setting header to 1
    rec_hdr.seqnbr = f.seqnum_client;
    
    //create packet
    ack_packet_rec.serv_hdr = rec_hdr;
    
    // not sending anything
    ack_packet_rec.len = 0; 
    ack_packet_rec.payload[0];
    
    //if the length of the payload is 0
    if(temp.len == 0){
        // setting connect_send_ack the info we need to send
        int connect_send_ack = sendto(socket_server, &ack_packet_rec, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));
        
        printf("** Type recieved = temp.hdr : %d\n", temp.hdr.type );
        printf("** seqnum  recieved = temp.hdr.seqnbr : %d\n", temp.hdr.seqnbr );
        printf("** Datagram length = temp.len =  %d\n", temp.len );
        printf("** ack_packet_rec.serv_hdr.type : %d\n", ack_packet_rec.serv_hdr.type );
        printf("** ack_packet_rec.len : %d\n", ack_packet_rec.len );
        
        if (connect_send_ack < 0) {
            //checking for error
            perror("Error");
            exit(EXIT_FAILURE);
        }
        else {
            printf(">> Sent ACK\n"); 
            //setting server state to 1 after sending ack
            f.state = 1;
            printf(">> The state is now ESTABLISHED - %d\n\n", f.state);
        } 
    }
    else{
        
        // printf("3 ack_packet_rec.serv_hdr.seqnbr in the if loop is:%d \n", ack_packet_rec.serv_hdr.seqnbr);
        test++;
        printf("** This is iteration %d \n", test);
        
        // if the length of the datagram !=0, then go on with the code
        // printf("** the temp.hdr.seqnbr is: %d\n", temp.hdr.seqnbr);
        // printf("** f.seqnum_client b4 if is %d\n", f.seqnum_client);
	printf("----- %d -----\n", sent_counter);
	printf("%d\n",temp.hdr.seqnbr);
	printf("%d\n",f.seqnum_client);
        if (temp.hdr.seqnbr == f.seqnum_client){
            //increment local counter for the next transmission
            f.seqnum_client++;
            //if the received seq number is equal to the local counter
            // printf("Datagram payload: %s\n", temp.payload);
            //  printf("2 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
            if (test_mode == 1) {
                
                if (sent_counter % 7 == 0 ) {
                    // Lose a packet
		    printf(">> Simulating a lost packet...\n");
                   
		    f.seqnum_client = temp.hdr.seqnbr;
                    
                } else if ( sent_counter % 4 == 0 ) {
                    // Delay a packet
                    printf(">> Simulating a packet delayed by 5 seconds...\n");
                    sleep(5);
                    
                    int receive_send_ack = sendto(socket_server, &ack_packet_rec, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));	 
                    
                    // printf("3 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
                    if (receive_send_ack = 0) {
                        //error checking
                        perror("|| On ACK sendto");
                        exit(EXIT_FAILURE);
                    }

		    // Writing the received datagram into an actual file
		    char portnum[10];
		    sprintf(portnum, "%s-%d-%s\n",str, server_port_num, new_date);
		    printf("%s\n", portnum);
		    // Declare out_file to output the received file
		    // Make the file if it doesnt exist
		    FILE * out_file;
		    out_file = fopen( portnum, "a");
		    
		    fprintf(out_file, "%s",temp.payload);
		    fclose(out_file);
                
            	    // ack_packet_rec.serv_hdr.seqnbr++;
		    // printf("2ack_packet_rec.serv_hdr.seqnbr in the if loop is:%d \n", ack_packet_rec.serv_hdr.seqnbr);
		    // printf("4 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
		    // printf("f.seqnum_client after is %d\n\n", f.seqnum_client);
		    f.last_return++;
		    //sent_counter++;
		    //recursively calling srtp_accept()
		    srtp_accept(&f);

                } else {
		  // Send normally
		  int receive_send_ack = sendto(socket_server, &ack_packet_rec, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));	                  
		  // printf("3 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
		  if (receive_send_ack = 0) {
		    //error checking
		    perror("|| On ACK sendto");
		    exit(EXIT_FAILURE);
		  }
		  // Writing the received datagram into an actual file
		  char portnum[10];
		  sprintf(portnum, "%s-%d-%s\n",str, server_port_num, new_date);
		  printf("%s\n", portnum);
		  // Declare out_file to output the received file
		  // Make the file if it doesnt exist
		  FILE * out_file;
		  out_file = fopen( portnum, "a");
		  fprintf(out_file, "%s",temp.payload);
		  fclose(out_file);
		             
		  // ack_packet_rec.serv_hdr.seqnbr++;
		  // printf("2ack_packet_rec.serv_hdr.seqnbr in the if loop is:%d \n", ack_packet_rec.serv_hdr.seqnbr);
		  // printf("4 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
		  // printf("f.seqnum_client after is %d\n\n", f.seqnum_client);
		  f.last_return++;
		  //sent_counter++;
		  //recursively calling srtp_accept()
		  srtp_accept(&f);
                }
                
            } else {
	      
	      int receive_send_ack = sendto(socket_server, &ack_packet_rec, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));	
              
	      // printf("3 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
	      if (receive_send_ack = 0) {
		//error checking
		perror("|| On ACK sendto");
		exit(EXIT_FAILURE);
	      }
              
            
            // Writing the received datagram into an actual file
            char portnum[10];
            sprintf(portnum, "%s-%d-%s\n",str, server_port_num, new_date);
            printf("%s\n", portnum);
            // Declare out_file to output the received file
            // Make the file if it doesnt exist
            FILE * out_file;
            out_file = fopen( portnum, "a");
	    fprintf(out_file, "%s",temp.payload);
	    fclose(out_file);
            
            
            
            // ack_packet_rec.serv_hdr.seqnbr++;
            // printf("2ack_packet_rec.serv_hdr.seqnbr in the if loop is:%d \n", ack_packet_rec.serv_hdr.seqnbr);
            // printf("4 ack_packet_rec.serv_hdr.seqnbr = %d\n", ack_packet_rec.serv_hdr.seqnbr);
            // printf("f.seqnum_client after is %d\n\n", f.seqnum_client);
            f.last_return++;
            sent_counter++;
            //recursively calling srtp_accept()
            srtp_accept(&f);
	    }
        }
        else{
            // resend the seq number that is just received
            //ack_packet_rec.serv_hdr.seqnbr = temp.hdr.seqnbr ;
            f.seqnum_client = temp.hdr.seqnbr;
            f.seqnum_client++;
            ack_packet_rec.serv_hdr.seqnbr = temp.hdr.seqnbr;
            // printf("** temp.hdr.seqnbr %d \n", temp.hdr.seqnbr );
            // printf("** ack_packet_rec.serv_hdr.seqnbr %d \n", ack_packet_rec.serv_hdr.seqnbr);
            int receive_send_ack = sendto(socket_server, &ack_packet_rec, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));
            // printf("** In the else loop\n\n");
	    sent_counter++;            
	    srtp_accept(&f);	        
        }
	      temp.hdr.seqnbr = f.seqnum_client;
    }

    if(temp.hdr.type == 5){
        srtp_server_reset(&f);
        printf("Resetting...\n");
    }
               
    //reads and prints the ip address
    inet_ntop(AF_INET, &(client_info.sin_addr), str, INET_ADDRSTRLEN);
    //	printf("This is the ip address: %s\n", str);
    
    //close(connfd);
    //	exit(0);
    // }
    //	 close(connfd);
    
}

/*
 Function:   srtp_server_reset
 Purpose:	Independent of the state of the connection (except for the CLOSED state),
 this function sends a RST packet to the client, and returns to the CLOSED state.
 
 Parameters: 
 spcb	: server PCB  
 
 Returns:     0 on success, -1 on failure
 */
int srtp_server_reset(struct srtp_server_pcb *spcb){
    
    // pktbuf for reset packet
    struct pktbuf reset;
    
    // Creating header for the reset packet 
    struct srtp_hdr reset_hdr;
    
    // Create header
    // Creating syn packet in header
    reset_hdr.type = 5; 
    
    // Setting header to 0
    reset_hdr.seqnbr = 0; 
    
    // Sending RST packet to client 
    int reset_send = sendto(socket_server, &reset, sizeof(struct pktbuf), 0,(struct sockaddr *)&client_info, sizeof(client_info));
    // printf("at srtp_server_rest, going back to listen\n");
    
    // Set PCB state to close
    f.state = 2;
    // Clean up counters and put them back to original state
    f.seqnum_client = 1;
    f.last_return = 1;
    test = 0;
    // Setting server state to 2 after sending ack
    f.state = 2;
    printf(">> The state is now CLOSED - %d\n", f.state);
    
    new_date = NULL;
    // printf("new_date after is : %s\n",new_date);
    // Going back to srtp_listen
    srtp_listen(&f, server_port_num);
}



/*
 Function:   srtp_receiver_loop
 Purpose:	This function handles all the ARQ processing on the server side. 
 The function implements a loop in which data packets are received from the client,
 and the corresponding ACKs are sent etc. To simulate passing data to the application
 the function simply writes data to the file with name 'outfile'. 
 
 The function also needs to handle the 2-way connection termination handshake.
 
 The function should react to any protocol error, i.e. unexpected behaviour of the client
 with calling srtp_server_reset() and aborting.
 
 Parameters: 
 spcb	: server PCB  
 outfile	: name of file we write data to
 
 Returns:     0 on success, -1 on failure
 */
//int srtp_receive_loop(struct srtp_server_pcb *spcb, char *outfile);


main (int argc, char *argv[]){
    if (argc != 3) {
        
        printf("Usage: \n %s <port> <test - 0/1>\n", argv[0]);
        
    } else {
        
        server_port_num = atoi(argv[1]);
        test_mode = atoi(argv[2]);
        
        // printf("current date is :%s \n", date);
        //	  char *localtime;
        // localtime = date;
        
        //setting local counter to be used in accept 
        f.seqnum_client = 1;
        f.last_return = 1;
        
        printf(">> The port number is %d\n", server_port_num);
        
        // Setting State
        f.state = 2; 
        f.server_port_num_pcb = server_port_num;
        printf(">> The state is now CLOSED - %d\n", f.state);
        
        srtp_listen(&f, server_port_num);
        // exit(EXIT_SUCCESS);//exiting thread
    } 
}
